package com.ciaojian.client.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.ciaojian.client.pay.WxPayBeanFactory;
import com.ciaojian.client.service.*;
import com.ciaojian.core.base.annotation.ApiIdempotent;
import com.ciaojian.core.enums.base.IBaseEnum;
import com.ciaojian.core.enums.business.*;
import com.ciaojian.core.exception.EntityNotFoundException;
import com.ciaojian.core.mapper.MyCouponMapper;
import com.ciaojian.core.mapper.UserMemberMapper;
import com.ciaojian.core.mapper.UserTimesCardMapper;
import com.ciaojian.core.model.*;
import com.ciaojian.core.model.dto.StoreDTO;
import com.ciaojian.core.model.dto.WorkerDTO;
import com.ciaojian.core.model.vo.CouponVo;
import com.ciaojian.core.service.UserTimesCardService;
import com.ciaojian.core.util.*;
import com.ciaojian.core.util.function.OrderFunctional;
import com.ciaojian.miniapp.api.WxMiniAppApi;
import com.ciaojian.miniapp.model.request.submessage.BaseTemplate;
import com.ciaojian.miniapp.model.request.submessage.TakeCommentMessage;
import com.ciaojian.miniapp.model.request.submessage.TakeCouponMessage;
import com.ciaojian.miniapp.model.request.submessage.TemplateIdEnum;
import com.ciaojian.miniapp.model.response.SubMessageSendResponse;
import com.ciaojian.pay.core.enums.SignType;
import com.ciaojian.pay.core.kit.WxPayKit;
import com.ciaojian.pay.wxpay.WxPayApi;
import com.ciaojian.pay.wxpay.model.request.UnifiedOrderModel;
import com.ciaojian.pay.wxpay.model.response.NotifyResponse;
import com.ciaojian.pay.wxpay.model.response.UnifiedOrderModelResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.context.annotation.Description;
import org.springframework.core.annotation.OrderUtils;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.ciaojian.core.enums.business.CouponCodeOriginEnum.BUY_MEMBER_DETAIL;


/**
 * @author Atlakyn
 */
@Api(tags = "支付接口")
@RestController
@RequestMapping("/pay")
public class PayController {
    @Resource
    private CouponCodeServiceImpl couponCodeService;
    @Resource
    private CouponServiceImpl couponService;
    @Resource
    private OrderServiceImpl orderService;
    @Resource
    private UserServiceImpl userService;
    @Resource
    private MyCouponServiceImpl myCouponService;
    @Resource
    private CouponPackageServiceImpl couponPackageService;
    @Resource
    private WorkerInviteServiceImpl workerInviteService;
    @Resource
    private UserMemberServiceImpl<UserMemberMapper, UserMember> userMemberService;
    @Resource
    private MemberOrderServiceImpl memberOrderService;
    @Resource
    private WorkerServiceImpl workerService;
    @Resource
    private OrderCouponRecordServiceImpl orderCouponRecordService;
    @Resource
    private StoreServiceImpl storeService;
    @Resource
    private UserTimesCardService<UserTimesCardMapper, UserTimesCard> userTimesCardService;
    @Resource
    private PayServiceImpl payService;
    @Resource
    private ProductGroupServiceImpl productGroupService;
    @Resource
    private MemberDetailServiceImpl memberDetailService;
    @Resource
    private NotifyServiceImpl notifyService;
    private ChargeUserServiceImpl chargeUserService;
    /**
     * kafka 主题名称
     */
    private static final String TOPIC_NAME = "user.register.topic";
    @Resource
    private KafkaTemplate<String, Object> kafkaTemplate;

    @ApiOperation("卡金余额支付")
    @PostMapping("/with-charge")
    public Result payWithCharge(Integer userId,
                                Integer orderId,
                                @ApiParam("数额") @NotNull @Min(1) Integer amount) {
        return Result.ok(chargeUserService.chargeChange(userId, amount, 0, 1, orderId));
    }

    @ApiOperation("充值卡金")
    @PostMapping("/charge")
    public Result charge(Integer chargeId, Integer userId, HttpServletRequest request) throws IllegalAccessException {
        Charge charge = Optional.of(new Charge().selectById(chargeId))
                .orElseThrow(() -> new EntityNotFoundException(Charge.class, "id", chargeId));
        String token = request.getHeader("token");
        //订单号
        String orderNo = orderService.genOrderNo(OrderNoTypeEnum.CHARGE);
        // 生成订单
        Order order = new Order();
        order.setUserId(userId);
        order.setType(charge.getName());
        order.setGoodsTotal(BigDecimal.valueOf(charge.getPrice() / 100));
        order.setPayTotal(BigDecimal.valueOf(charge.getPrice() / 100));
        order.setOrderNo(orderNo);
        order.setCouponId(chargeId);
        order.setStatus(OrderStatusEnum.wait_pay);
        order.setOrderType(OrderOrderTypeEnum.CHARGE);
        order.insert();

        WxPayBase wxPayBean = WxPayBeanFactory.getInstance(userService.getUserInviterStore(userId), 1);
        String openid = TokenUtil.getUserOpenid(token);
        // 收银台参数
        Map<String, Object> packageParams = payService.pay2(order, openid, wxPayBean, OrderOrderTypeEnum.CHARGE, request);
        return Result.ok(packageParams);
    }

    @ApiIdempotent
    @ApiOperation("次卡支付")
    @PostMapping("/use-times-card")
    public Result useTimesCard(String orderNo, Integer userTimesCardId) {
        // 校验订单状态
        Order order = orderService.getByOrderNo(orderNo);
        // 校验订单状态
        Assert.isTrue(order.getStatus().equals(OrderStatusEnum.wait_pay)
                || order.getStatus().equals(OrderStatusEnum.servicing)
                || order.getStatus().equals(OrderStatusEnum.sling), "订单状态不是待付款");

        // 判断用户次卡是否有效(有效时间、适用服务、适用门店)
        Optional<UserTimesCard> optional = userTimesCardService.selectById(userTimesCardId);
        Assert.isTrue(optional.isPresent(), "次卡不存在");
        UserTimesCard userTimesCard = optional.get();
        Assert.isTrue(userTimesCard.getDueDate().after(new Date()), "次卡已过期");

        // 修改订单信息
        // 顾客当前订单之前已完成和已支付的订单数
        order.setStatus(OrderStatusEnum.paid)
                .setPayType(PayTypeEnum.TIMES_CARD.getDescription())
                .setPayTime(new Date())
                .setPayTotal(BigDecimal.ZERO)
                .setCouponTotal(order.getGoodsTotal());
        order.setAddTime(new Date());
        orderService.setCustomerType(order);
        orderService.setWorker(order);
        order.updateById();

        // 记录次卡使用次数
        userTimesCard.setTimes(userTimesCard.getTimes() + 1).updateById();
        // 首次消费,为送券人发送微信红包
        // 首次消费,为分销员发送微信红包
        return Result.ok();
    }

    @ApiOperation("购买产品")
    @PostMapping("/product-group")
    public Result payProductGroup(Integer userId,
                                  Integer productId,
                                  String openId,
                                  HttpServletRequest request
    ) throws IllegalAccessException {
        User user = new User().selectById(userId);

        // 购买的产品组
        ProductGroup productGroup = productGroupService.getById(productId).orElseThrow(
                () -> new EntityNotFoundException(ProductGroup.class, "id", productId)
        );

        return Result.ok(payService.pay(productGroup, user, openId,
                WxPayBeanFactory.getInstance(userService.getUserInviterStore(userId), 0),
                OrderOrderTypeEnum.DOUYIN_WRITE_OFF, request));
    }

    @ApiOperation("购买产品")
    @PostMapping("/product-group/v2")
    public Result payProductGroupV2(Integer userId,
                                    Integer productId,
                                    String openId,
                                    Integer orderType,
                                    HttpServletRequest request
    ) throws IllegalAccessException {
        User user = new User().selectById(userId);

        // 购买的产品组
        ProductGroup productGroup = productGroupService.getById(productId).orElseThrow(
                () -> new EntityNotFoundException(ProductGroup.class, "id", productId)
        );

        return Result.ok(payService.pay(productGroup, user, openId,
                WxPayBeanFactory.getInstance(userService.getUserInviterStore(userId), 0),
                OrderOrderTypeEnum.DOUYIN_WRITE_OFF, request));
    }

    @ApiOperation("微信支付-购买会员卡")
    @PostMapping("/wxpay/member-card/{userId}/{memberId}")
    public Result wxPayOfMemberCard(HttpServletRequest request,
                                    @PathVariable Integer userId,
                                    @PathVariable Integer memberId
    ) throws IllegalAccessException {
        LogUtil.writePayLog(">>>购买会员卡支付方法>>>方法参数->{userId:{},memberId:{}}", userId, memberId);

        // 生成订单号
        String orderNo = memberOrderService.getCurrentDateStr();
        String token = request.getHeader("token");

        Member member = new Member().selectById(memberId);

        // 新增会员卡订单
        MemberOrder memberOrder = memberOrderService.insert(userId, memberId, workerInviteService.getWorkerIdByUserId(userId), orderNo);

        // 根据UserId 查询 WorkerId
        Integer workerId = workerInviteService.getWorkerIdByUserId(userId);
        memberOrder.setInviteId(workerId);
        StoreDTO storeDTO = storeService.getBaseMapper().findByWorkerId(workerId);

        String body = member.getTitle();

        try {
            if (storeDTO != null) {
                memberOrder.setStoreId(storeDTO.getId());
                body = storeService.getTitleById(memberOrder.getStoreId()).concat("-").concat(body);
            }
        } catch (Exception e) {
            e.printStackTrace();
            LogUtil.writeErrorLog(e.getMessage());
        }

        WxPayBase wxPayBean = WxPayBeanFactory.getInstance(userService.getUserInviterStore(userId), 1);
        String openid = TokenUtil.getUserOpenid(token);
        Map<String, Object> params = UnifiedOrderModel
                .builder()
                .appid(wxPayBean.getAppId())
                .mch_id(wxPayBean.getMchId())
                .nonce_str(RandomUtil.randomString(32))
                .body(body)
                .out_trade_no(orderNo)
                .total_fee(String.valueOf(member.getPrice().intValue()))
                .spbill_create_ip(StringUtil.getIpAddr(request))
                .notify_url(wxPayBean.getDomain().concat("/client/notify/wxNotify/member-card"))
                .trade_type(wxPayBean.getTradeType())
                .openid(openid)
                .build()
                .createSign(wxPayBean.getKey(), SignType.MD5);
        LogUtil.writePayLog(">>>购买会员卡支付方法>>>请求参数:{params:{}}", params);
        // 返回 xml 格式表单
        String xmlResult = WxPayApi.pushOrder(params);
        UnifiedOrderModelResponse response = WxPayKit.xmlToBean(xmlResult, UnifiedOrderModelResponse.class);
        LogUtil.writePayLog(">>>payMethod,支付请求返回参数={}>>>", response.toString());

        // 判断请求结果
        WxPayKit.codeIsOk(response);

        // 微信支付返回的预支付id
        String prepayId = response.getPrepay_id();
        // 二次签名，构建公众号唤起支付的参数,这里的签名方式要与上面统一下单请求签名方式保持一致
        Map<String, Object> packageParams = WxPayKit.miniAppPrepayIdCreateSign(
                wxPayBean.getAppId(),
                prepayId,
                wxPayBean.getKey(),
                SignType.MD5);
        // 将二次签名构建的数据返回给前端并唤起小程序支付
        return Result.ok(packageParams);
    }

//    @PostMapping("/hair-cut")
//    public Result hairCut(HttpServletRequest request,
//                          String orderNo,
//                          String[] codes,
//                          String type,
//                          Integer memberDiscountId) {
//
//    }

    /**
     * @param orderNo          订单号
     * @param type             类型
     * @param memberDiscountId 用户优惠 id
     */
    @ApiIdempotent
    @Transactional
    @ApiOperation("剪发支付")
    @PostMapping("/wxPay/v2")
    public synchronized Result wxPayV2(HttpServletRequest request,
                                       @ApiParam("订单号") String orderNo,
                                       @ApiParam("优惠券码") String[] codes,
                                       @ApiParam("服务类型") String type,
                                       @ApiParam("用户优惠 id") Integer memberDiscountId
    ) throws IllegalAccessException, JsonProcessingException {
        Assert.hasText(orderNo, "订单号不能为空");
        Assert.hasText(type, "服务项目不能为空");
        QueryWrapper<Order> wrapper = new QueryWrapper<>();
        wrapper.eq("order_no", orderNo);
        Order order = orderService.getOne(wrapper, true);
        // 订单号
        if (order == null) {
            LogUtil.writePayLog(">>>wxPay>>>订单{}不存在", orderNo);
            return Result.error("订单不存在");
        }
        if (!order.getStatus().equals(OrderStatusEnum.wait_pay) && !order.getStatus().equals(OrderStatusEnum.servicing)
                && !order.getStatus().equals(OrderStatusEnum.sling) && !order.getStatus().equals(OrderStatusEnum.wait_notify)) {
            LogUtil.writePayLog(">>>wxPay>>>订单[id={},orderNo={}]的状态={}不是待付款", order.getId(), order.getOrderNo(), order.getStatus());
            return Result.error("订单当前不可付款");
        }

        //统计扫码小程序码进来剪发的订单
        try {
            String key = RedisUtil.getKey("metro::user::" + order.getUserId());
            if (ObjectUtil.isNotNull(key)) {
                Store store = new Store().selectById(order.getStoreId());
                store.setQrHair(store.getQrHair()+1);
                store.updateById();
            }
        }catch (Exception e){}

        // 这里需要修改一次订单号
        // 因为当用户取消支付然后重新选择优惠后,价格产生变动
        // 此时同一个订单号但是不同的价格参数会被微信拒绝
        // 修改订单号相当于对微信发起了一个新的支付订单
        // 新订单号
        String newOrderNo = orderService.generateOrderNoByOrderId(order.getId());
        LogUtil.writePayLog(">>>wxPay>>>新订单号={},旧订单号={}", newOrderNo, orderNo);

        String token = request.getHeader("token");
        // 订单总价
        final BigDecimal money = order.getGoodsTotal();

        // 根据订单ID查询用户ID
        Integer userId = order.getUserId();
        // 拿到用户获取会员的时间
        MyCouponMapper baseMapper = myCouponService.getBaseMapper();
        // 如果用户优惠 id 不为 null 进这个判断
        if (memberDiscountId != null) {
            // 会员优惠
            MemberDiscount memberDiscount = new MemberDiscount().selectById(memberDiscountId);
            // 校验优惠是否有效
            if (memberDiscount == null || memberDiscount.getOrderId() != null) {
                LogUtil.writePayLog(">>>优惠不存在或已被使用,memberDiscount={}>>>", memberDiscount);
                return Result.error("优惠不存在或已被使用");
            }
            // 校验 memberDiscount 的 userId 和 order 的 userId 是否一致
            if (!memberDiscount.getUserId().equals(order.getUserId())) {
                LogUtil.writePayLog(">>>优惠所属用户和订单用户不一致,memberDiscount.getUserId():{},order2.getUserId():{}>>>", memberDiscount, order.getUserId());
                return Result.error("优惠所属用户和订单用户不一致");
            }

            LogUtil.writePayLog(">>>wxPay,memberDiscount:{},userId:{}>>>", memberDiscount, order.getUserId());
            // 支付时先把订单号放进去,在支付成功回调的方法处再把订单 id 放进去,订单 id 不为 null 时才表示已被使用
            memberDiscount.setOrderNo(newOrderNo);
            memberDiscount.updateById();
        } else {
            // 用户使用了优惠劵
            List<String> codeList = Arrays.asList(codes);
            BigDecimal reductionTotal = BigDecimal.ZERO;
            if (CollUtil.isNotEmpty(codeList)) {
                for (String code : codeList) {
                    CouponVo couponVo = baseMapper.getCouponByCode(code);
                    Assert.notNull(couponVo, "优惠券不存在");
                    switch (couponVo.getCouponType()) {
                        // 满减券
                        case 0:
                            reductionTotal = reductionTotal.add(couponVo.getReduction());
                            break;
                        // 折扣券
                        case 1:
                            reductionTotal = reductionTotal.add(money.multiply(couponVo.getDiscount()));
                            break;
                        default:
                    }
                    // 用户使用了抖音券, 用Redis缓存, 如果用户后续购买了会员权益, 会赠送相应的优惠券
                    couponCodeService.usedDouyinCouponCode(couponVo, order.getUserId());
                    // 新增券码使用记录
                    new OrderCouponRecord()
                            .setCouponId(couponVo.getId())
                            .setCouponCode(couponVo.getCode())
                            .setCouponTitle(couponVo.getTitle())
                            .setOrderId(order.getId())
                            .setUserId(order.getUserId())
                            .insert();
                }
                order.setCouponTotal(reductionTotal);
            }
            // 用户没有使用优惠劵
            else {
                UpdateWrapper<Order> updateWrapper = new UpdateWrapper<>();
                updateWrapper.set("coupon_code", null);
                updateWrapper.set("coupon_total", 0);
                updateWrapper.eq("order_no", newOrderNo);
                orderService.update(updateWrapper);
            }
        }
        // 计算优惠后的价格
        BigDecimal discountPrice = orderService.calDiscountV2(order, Arrays.asList(codes), type, memberDiscountId, money, userId);
        return payMethod(request, token, newOrderNo, discountPrice, order, type);
    }

    @ApiOperation("剪发支付取消")
    @ApiImplicitParam(name = "订单ID", value = "orderId", paramType = "query")
    @PutMapping("/wxpay/cancel")
    public Result cancelWxPay(@NotNull(message = "订单Id不能为空") Integer orderId) {
        Order order = new Order().selectById(orderId);
        Assert.notNull(order, "订单不能为空");
        order.setCouponTotal(null);

        orderCouponRecordService.lambdaUpdate()
                .eq(OrderCouponRecord::getOrderId, orderId)
                .remove();

        return Result.save(order.updateById());
    }

    /**
     * 支付方法
     *
     * @param request http request
     * @param token   用户 token
     * @param orderNo 订单号
     * @param money   金额
     * @param order   店面名称
     * @param type    服务类型
     * @return 支付结果
     */
    private Result payMethod(HttpServletRequest request,
                             @RequestHeader String token,
                             String orderNo,
                             BigDecimal money,
                             Order order,
                             String type) throws IllegalAccessException {
        String openid = TokenUtil.getUserOpenid(token);
        if (money.compareTo(new BigDecimal("0.01")) < 0) {
            money = new BigDecimal("0.01");
        }

        WxPayBase wxPayBean = WxPayBeanFactory.getInstance(storeService.getById(order.getStoreId()), 0);

        //组装参数，用户生成统一下单接口的签名
        Map<String, Object> params = UnifiedOrderModel
                .builder()
                .appid(wxPayBean.getAppId())
                .mch_id(wxPayBean.getMchId())
                .nonce_str(RandomUtil.randomString(32))
                .body(order.getStoreName() + "-" + type)
                .out_trade_no(orderNo)
                // 微信支付要求金额以分为单位,并且不能存在小数
                // 所以这里从元转成分,并且去掉小数位
                .total_fee(BigDecimalUtil.increase(money, BigDecimalUtil.HUNDRED, 0).toString())
                .spbill_create_ip(StringUtil.getIpAddr(request))
                .notify_url(wxPayBean.getDomain().concat("/client/pay/jianfa/wxNotify"))
                .trade_type(wxPayBean.getTradeType())
                .openid(openid)
                .build()
                .createSign(wxPayBean.getKey(), SignType.MD5);
        LogUtil.writePayLog(">>>payMethod,请求参数:params={}>>>", params);
        // 返回 xml 格式表单
        String xmlResult = WxPayApi.pushOrder(params);
        UnifiedOrderModelResponse response = WxPayKit.xmlToBean(xmlResult, UnifiedOrderModelResponse.class);
        LogUtil.writePayLog(">>>payMethod,,支付请求返回参数={}>>>", response.toString());

        // 更新订单信息
        order.setTotal(money);
        // 修改订单号
        order.setOrderNo(orderNo);
        order.updateById();

        // 将二次签名构建的数据返回给前端并唤起小程序支付
        Map<String, Object> packageParams = new HashMap<>(7);
        // 返回一个新的订单号给前端
        packageParams.put("orderNo", orderNo);
        // 请求失败
        WxPayKit.codeIsOk(response);
        // 微信支付返回的预支付id
        String prepayId = response.getPrepay_id();
        // 二次签名，构建公众号唤起支付的参数,这里的签名方式要与上面统一下单请求签名方式保持一致
        packageParams.putAll(WxPayKit.miniAppPrepayIdCreateSign(
                wxPayBean.getAppId(),
                prepayId,
                wxPayBean.getKey(),
                SignType.MD5));
        return Result.ok(packageParams);
    }

    /**
     * 这里是支付回调接口，微信支付成功后会自动调用
     */
    @ApiOperation(value = "剪发微信支付回调", hidden = true)
    @Transactional(rollbackFor = Exception.class)
    @PostMapping(value = "/jianfa/wxNotify")
    public void jianfaWxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // todo 建立一个回调队列,如果接口出现异常则把订单放进队列,再通过主动向微信请求订单信息来更新订单
        String notifyXml = getNotifyXml(request);
        NotifyResponse notifyResponse = WxPayKit.xmlToBean(notifyXml, NotifyResponse.class);
        LogUtil.writePayLog(">>>jianfaWxNotify notifyXml 信息>>>map={}", notifyResponse.toString());
        // 获取商户系统内部订单号（即取号时生成的订单编号)
        String orderNo = notifyResponse.getOut_trade_no();
        // 获取订单id
        String orderId = orderNo.substring(orderNo.indexOf("I") + 1);
        LogUtil.writePayLog(">>>jianfaWxNotify>>>订单号={}的订单id={}", orderNo, orderId);
        // 获取订单
        Order order = new Order().selectById(orderId);
        LogUtil.writePayLog(">>>jianfaWxNotify>>>订单={}", order);
        if (checkJianFaNotify(order)) {
            // 微信支付金额,此时还是以分为单位,所以转成 元为单位
            BigDecimal totalFee = BigDecimalUtil.reduce(notifyResponse.getTotal_fee(), BigDecimalUtil.HUNDRED);
            // 金额校验
            if (BigDecimalUtil.compare(totalFee, order.getTotal()) == 0) {
                orderService.payNotify(order);
                try {
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                    String expiredTime = simpleDateFormat.format(new Date());

                    BaseTemplate baseTemplate = BaseTemplate.builder(TemplateIdEnum.COMMENT_TO_EXPIRE)
                            .touser(notifyResponse.getOpenid())
                            .data(TakeCommentMessage.builder()
                                    .thing1(order.getStoreName())
                                    .time2(expiredTime)
                                    .thing3("反馈评价可随机获取剪发券奖励!")
                                    .build()
                            );
                    SubMessageSendResponse response1 = WxMiniAppApi.subscribeMessageSend(baseTemplate);
                    if (response1.getErrcode() == 0) {

                    } else {
                        LogUtil.writeBusinessLog("提醒评价通知失败CommentExpired: {}", response1.getErrmsg());
                    }
                } catch (Exception e) {
                    LogUtil.writeErrorLog(e.getMessage());
                }

                // 通知微信回调成功
                notifyResponse.notifyWx(response);
            }
        }
    }

    /**
     * 判断订单状态是否允许回调
     *
     * @param order 订单
     * @return 订单为 等待付款,待付款,服务中,等待中 则返回 true
     */
    private boolean checkJianFaNotify(Order order) {
        if (order == null) {
            return false;
        }
        if (order.getStatus().equals(OrderStatusEnum.wait_notify)) {
            return true;
        }
        if (order.getStatus().equals(OrderStatusEnum.wait_pay)) {
            return true;
        }
        if (order.getStatus().equals(OrderStatusEnum.servicing)) {
            return true;
        }
        if (order.getStatus().equals(OrderStatusEnum.sling)) {
            return true;
        }
        return false;
    }

    /**
     * @param orderId 订单 id
     * @return 修改结果
     */
    @ApiOperation("取消支付,取消后状态改回待支付")
    @PostMapping("/front-end/pay/cancel/{orderId}")
    public Result frontEndPayCancel(@PathVariable Integer orderId) {
        LogUtil.writePayLog(">>>frontEndPayCancel 解锁订单id={}>>>", orderId);
        // 取消支付
        return Result.ok(orderService.unlockOrder(orderId));
    }

    @ApiOperation("购买优惠券")
    @PostMapping("/coupon/{couponId}")
    public Result payCoupon(@PathVariable @ApiParam("券id") Integer couponId,
                            @ApiParam("用户id") Integer userId,
                            HttpServletRequest request) throws IllegalAccessException {
        Coupon coupon = new Coupon().selectById(couponId);
        Assert.notNull(coupon, "券不存在");
        Assert.isTrue(orderService.checkNotSold(couponId, userId), "您已购买过此券");
        // 校验券是否还有库存
        Boolean sufficient = couponService.checkStockSufficient(couponId);
        Assert.isTrue(sufficient, "本券已售罄");

        String openId = userService.getOpenIdById(userId);
        // 生成订单号
        OrderFunctional orderFunctinoal = (type) -> {
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            return type.getType() + sdf.format(date);
        };
        //订单号
        String orderNo = orderFunctinoal.generateOrderNo(OrderNoTypeEnum.COUPON);
        // 生成订单
        Order order = new Order();
        order.setUserId(userId);
        order.setType(coupon.getTitle());
        order.setGoodsTotal(coupon.getBuyNum());
        order.setOrderNo(orderNo);
        order.setStatus(OrderStatusEnum.wait_notify);
        order.setCouponId(couponId);
        order.setOrderType(OrderOrderTypeEnum.COUPON);
        order.insert();
        // TODO 放进 kafka 延时处理
//        kafkaTemplate.send(TOPIC_NAME, order);


        WxPayBase wxPayBean = WxPayBeanFactory.getWxPayBean();
        Map<String, Object> params = UnifiedOrderModel
                .builder()
                .appid(wxPayBean.getAppId())
                .mch_id(wxPayBean.getMchId())
                .nonce_str(RandomUtil.randomString(32))
                .body(coupon.getTitle())
                .out_trade_no(orderNo)
                .total_fee(String.valueOf(BigDecimalUtil.increase(coupon.getBuyNum(), BigDecimalUtil.HUNDRED, 0)))
                .spbill_create_ip(StringUtil.getIpAddr(request))
                .notify_url(wxPayBean.getDomain().concat("/client/pay/coupon/notify"))
                .trade_type(wxPayBean.getTradeType())
                .openid(openId)
                .build()
                .createSign(wxPayBean.getKey(), SignType.MD5);
        LogUtil.writePayLog(">>>购买优惠券支付方法>>>请求参数:{params:{}}", params);
        // 返回 xml 格式表单
        String xmlResult = WxPayApi.pushOrder(params);
        UnifiedOrderModelResponse response = WxPayKit.xmlToBean(xmlResult, UnifiedOrderModelResponse.class);
        LogUtil.writePayLog(">>>购买会员卡支付方法,支付请求返回参数={}>>>", response.toString());
        // 请求失败
        WxPayKit.codeIsOk(response);


        // 微信支付返回的预支付id
        String prepayId = response.getPrepay_id();
        // 二次签名，构建公众号唤起支付的参数,这里的签名方式要与上面统一下单请求签名方式保持一致
        Map<String, Object> packageParams = WxPayKit.miniAppPrepayIdCreateSign(
                wxPayBean.getAppId(),
                prepayId,
                wxPayBean.getKey(),
                SignType.MD5);
        // 预支付的券码id
//        packageParams.put("couponCodeId", couponCode.getId());
        // 将二次签名构建的数据返回给前端并唤起小程序支付
        return Result.ok(packageParams);
    }

    @ApiOperation(value = "取消购买优惠券订单")
    @PostMapping("/coupon/cancel/{couponCodeId}")
    public Result cancelPay(@PathVariable @ApiParam("券码id") Integer couponCodeId) {
        return couponCodeService.cancelPay(couponCodeId) ? Result.ok("取消成功") : Result.error("取消失败");
    }

    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "购买优惠券回调", hidden = true)
    @PostMapping("/coupon/notify")
    public void payCouponNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String notifyXml = getNotifyXml(request);
        String resXml;
        Map<String, Object> map = WxPayKit.xmlToMap(notifyXml);
        LogUtil.writePayLog(">>>支付回调微信返回 notifyXml 信息>>>map={}", map.toString());
        //获取商户系统内部订单号（即取号时生成的订单编号)
        String orderNo = map.get("out_trade_no").toString();
        LogUtil.writePayLog(">>>微信支付回调>>>订单号={}", orderNo);
        // 获取订单
        QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        Order order = new Order().selectOne(queryWrapper);
        LogUtil.writePayLog(">>>微信支付回调>>>订单={}", order);
        // 订单不为空 且 状态是 已支付-待回调 或者 待支付
        if (order != null && order.getStatus().equals(OrderStatusEnum.wait_notify)) {
            WxPayBase wxPayBean = WxPayBeanFactory.getInstance(userService.getUserInviterStore(order.getUserId()), 1);
            if (WxPayKit.verifyNotify(map, wxPayBean.getKey())) {
                BigDecimal totalFee = BigDecimalUtil.reduce(new BigDecimal(map.get("total_fee").toString()), BigDecimalUtil.HUNDRED);
                LogUtil.writePayLog(">>>微信支付回调>>>订单号={}的totalFee={}", orderNo, totalFee);
                // 购买后给用户新增优惠券
                if (notifyService.buyCouponCodeNotify(order)) {
                    // 通知微信收到回调
                    resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
                    BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                    out.write(resXml.getBytes());
                    out.flush();
                    out.close();
                }
            }
        }
    }

    /**
     * 获取微信回调xml
     *
     * @param request request
     * @return xml回调信息
     */
    private String getNotifyXml(HttpServletRequest request) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
        String line;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        // sb为微信返回的xml
        return sb.toString();
    }

    @ApiOperation("购买卡包")
    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/coupon-package/{orderType}/{couponPackageId}")
    public Result payCouponPackage(HttpServletRequest request,
                                   @PathVariable int orderType,  // 15积分商城 3次卡
                                   @PathVariable Integer couponPackageId,
                                   @RequestParam Integer userId
    ) throws IllegalAccessException {
        Assert.notNull(couponPackageId, "请选择卡包");
        Assert.isTrue(couponPackageService.checkRemainById(couponPackageId), "卡包已售罄");

        // 生成订单号
        OrderFunctional orderFunctional = (type) -> {
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            return type.getType() + sdf.format(date);
        };

        List<Integer> couponCodeList = new ArrayList<>();
        // 卡包
        CouponPackage couponPackage = new CouponPackage().selectById(couponPackageId);
        Assert.notNull(couponPackage, "卡包不存在");
        for (String couponId : couponPackage.getCoupons().split(",")) {
            // 循环卡包 分配券码
            CouponCode stock = couponCodeService.takeCode(userId, Integer.valueOf(couponId));
            Assert.notNull(stock, "卡包已售罄");
            couponCodeList.add(stock.getId());
        }

        // 判断重复订单
        Order order = orderService.getRepeat(couponPackageId, userId, 6);
        String orderNo;
        String openId = userService.getOpenIdById(userId);
        // 订单重复表示 15 分钟内提交过订单
        if (order != null) {
            orderNo = order.getOrderNo();
        } else {
            //订单号
            orderNo = orderFunctional.generateOrderNo(OrderNoTypeEnum.COUPON_PACKAGE);
            if (orderType == 15) {
                orderNo = orderFunctional.generateOrderNo(OrderNoTypeEnum.JFCARD);
            }

            // 生成订单
            order = new Order();
            order.setUserId(userId);
            order.setType(couponPackage.getName());
            order.setGoodsTotal(BigDecimalUtil.reduce(String.valueOf(couponPackage.getPrice()), BigDecimalUtil.HUNDRED));
            order.setOrderNo(orderNo);
            order.setStatus(OrderStatusEnum.wait_notify);
            order.setAddTime(new Date());

            order.setOrderType(OrderOrderTypeEnum.COUPON_PACKAGE);

            if (orderType == 15){
                //积分商城
                order.setOrderType(OrderOrderTypeEnum.JFCARD);
            }

            order.setCouponId(couponPackageId);
            Integer inviteId = workerInviteService.getWorkerIdByUserId(userId);
            if (inviteId != null) {
                WorkerDTO workerDTO = workerService.getBaseMapper().selectWorkerDTO(inviteId);
                order.setStoreId(workerDTO.getStore().getId());
                order.setStoreName(workerDTO.getStore().getTitle());
                order.setInviterId(inviteId);
            }
            order.setPayType("微信支付");
            // 获取
            order.insert();
        }

        WxPayBase wxPayBean = WxPayBeanFactory.getInstance(userService.getUserInviterStore(userId), 1);

        Map<String, Object> params = UnifiedOrderModel
                .builder()
                .appid(wxPayBean.getAppId())
                .mch_id(wxPayBean.getMchId())
                .nonce_str(RandomUtil.randomString(32))
                .body(couponPackage.getName())
                .out_trade_no(orderNo)
                .total_fee(String.valueOf(couponPackage.getPrice()))
                .spbill_create_ip(StringUtil.getIpAddr(request))
                .notify_url(wxPayBean.getDomain().concat("/client/pay/coupon-package/notify"))
                .trade_type(wxPayBean.getTradeType())
                .openid(openId)
                .build()
                .createSign(wxPayBean.getKey(), SignType.MD5);
        LogUtil.writePayLog(">>>购买卡包请求参数:{params:{}}>>>", params);
        // 返回 xml 格式表单
        String xmlResult = WxPayApi.pushOrder(params);
        UnifiedOrderModelResponse response = WxPayKit.xmlToBean(xmlResult, UnifiedOrderModelResponse.class);
        LogUtil.writePayLog(">>>购买卡包支付请求返回参数={}>>>", response.toString());
        // 判断请求结果
        WxPayKit.codeIsOk(response);

        // 微信支付返回的预支付id
        String prepayId = response.getPrepay_id();
        // 二次签名，构建公众号唤起支付的参数,这里的签名方式要与上面统一下单请求签名方式保持一致
        Map<String, Object> packageParams = WxPayKit.miniAppPrepayIdCreateSign(
                wxPayBean.getAppId(),
                prepayId,
                wxPayBean.getKey(),
                SignType.MD5);
        packageParams.put("couponCodeList", couponCodeList);
        // 将二次签名构建的数据返回给前端并唤起小程序支付
        return Result.ok(packageParams);
    }

    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "取消购买优惠卡包")
    @PostMapping("/coupon-package/cancel")
    public Result cancelPayCouponPackage(@ApiParam("券码id数组") @RequestParam List<Integer> couponCodeIds) {
        Assert.isTrue(CollUtil.isNotEmpty(couponCodeIds), "请选择要取消的卡包");
        return couponCodeService.cancelPay(couponCodeIds) ? Result.ok("取消成功") : Result.error("取消失败");
    }

    @Transactional(rollbackFor = Exception.class)
    @ApiOperation("取消购买会员权益")
    @PostMapping("/member-detail/cancel")
    public Result cancelPayMemberDetail(Integer orderId) {
        Assert.notNull(orderId, "订单id不能为空");
        Order order = new Order().selectById(orderId);
        Assert.notNull(order, "请选择订单");
        order.setStatus(OrderStatusEnum.cancel);
        order.updateById();
        LogUtil.writeBusinessLog(String.format("取消购买会员权益:订单:{%s}", order));
        return Result.ok();
    }

    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "购买卡包回调", hidden = true)
    @PostMapping("/coupon-package/notify")
    public void payCouponPackageNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String notifyXml = getNotifyXml(request);
        String resXml;
        Map<String, Object> map = WxPayKit.xmlToMap(notifyXml);
        LogUtil.writePayLog(">>>购买卡包回调 notifyXml 信息>>>map={}", map.toString());
        //获取商户系统内部订单号（即取号时生成的订单编号)
        String orderNo = map.get("out_trade_no").toString();
        LogUtil.writePayLog(">>>购买卡包回调>>>订单号={}", orderNo);
        // 获取订单
        QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        Order order = new Order().selectOne(queryWrapper);
        LogUtil.writePayLog(">>>购买卡包回调>>>订单={}", order);
        // 订单不为空 且 状态是 已支付-待回调 或者 待支付
        if (order != null && order.getStatus().equals(OrderStatusEnum.wait_notify)) {
            WxPayBase wxPayBean = WxPayBeanFactory.getInstance(userService.getUserInviterStore(order.getUserId()), 1);
            if (WxPayKit.verifyNotify(map, wxPayBean.getKey())) {
                BigDecimal totalFee = BigDecimalUtil.reduce(new BigDecimal(map.get("total_fee").toString()), BigDecimalUtil.HUNDRED);
                LogUtil.writePayLog(">>>购买卡包回调>>>订单号={}的totalFee={}", orderNo, totalFee);
                // 购买后给用户新增优惠券
                if (couponCodeService.buyCouponPackage(order)) {
                    // 券数量+1
                    couponService.addGetNumByCouponPackageId(order.getCouponId());
                    // 通知微信收到回调
                    resXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
                    BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                    out.write(resXml.getBytes());
                    out.flush();
                    out.close();
                }
            }
        }
    }

    @ApiOperation("权益免单")
    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/member-detail/{memberDetailId}/free")
    public Result payMemberDetailFree(@PathVariable Integer memberDetailId,
                                      @NotNull(message = "用户Id不能为空") Integer userId) {
        MemberDetail memberDetail = new MemberDetail().selectById(memberDetailId);
        Assert.notNull(memberDetail, "会员权益不存在");
        // 判断是不是会员
        Integer isUserMember = userMemberService.checkUserMember(userId);
        Assert.isTrue(isUserMember == 1, "您不是会员");

        LambdaQueryWrapper<Order> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Order::getUserId, userId);
        lambdaQueryWrapper.eq(Order::getCouponId, memberDetailId);
        lambdaQueryWrapper.eq(Order::getOrderType, OrderOrderTypeEnum.MEMBER_DETAIL);
        Assert.isTrue(orderService.count(lambdaQueryWrapper) == 0, "您已购买过此权益");

        // 生成订单号
        OrderFunctional orderFunctional = (type) -> {
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            return type.getType() + sdf.format(date);
        };
        String orderNo = orderFunctional.generateOrderNo(OrderNoTypeEnum.MEMBER_DETAIL);

        // 生成订单
        Order order = new Order();
        order.setUserId(userId);
        order.setType(memberDetail.getName());
        // 价格
        order.setGoodsTotal(BigDecimal.ZERO);
        order.setOrderNo(orderNo);
        order.setStatus(OrderStatusEnum.complete);
        order.setOrderType(OrderOrderTypeEnum.MEMBER_DETAIL);
        order.setCouponId(memberDetailId);
        order.insert();

        // 给用户发放优惠券
        memberDetailService.distributeCode(userId, Optional.of(memberDetail), order, BUY_MEMBER_DETAIL);
        return Result.ok();
    }

    @ApiOperation("购买会员权益")
    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/member-detail/{memberDetailId}")
    public Result payMemberDetail(Integer userId, @PathVariable Integer memberDetailId, HttpServletRequest request,@RequestParam(required = false) Integer channelType) throws IllegalAccessException {
        try {
            Assert.notNull(userId, "userId不能为空");
            Assert.notNull(memberDetailId, "请选择要购买的权益");

            MemberDetail memberDetail = new MemberDetail().selectById(memberDetailId);
            Assert.notNull(memberDetail, "会员权益不存在");

            // 判断是不是会员
            Integer isUserMember = userMemberService.checkUserMember(userId);
            Assert.isTrue(checkPayMemberDetail(memberDetail.getOnlyBuy(), isUserMember == 1), "此权益仅限会员购买");

            // 生成订单号
            OrderFunctional orderFunctional = (type) -> {
                Date date = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
                return type.getType() + sdf.format(date);
            };
            String orderNo = orderFunctional.generateOrderNo(OrderNoTypeEnum.MEMBER_DETAIL);

            // 生成订单
            Order order = new Order();
            order.setUserId(userId);
            order.setType(memberDetail.getName());
            // 价格
            Integer goodsTotal = isUserMember == 1 ? memberDetail.getPriceMember() : memberDetail.getPriceNormal();

            order.setGoodsTotal(new BigDecimal(goodsTotal).divide(BigDecimalUtil.HUNDRED).setScale(2, RoundingMode.HALF_UP));

            order.setOrderNo(orderNo);
            order.setStatus(OrderStatusEnum.wait_notify);
            order.setOrderType(OrderOrderTypeEnum.MEMBER_DETAIL);
            order.setCouponId(memberDetailId);
            order.setChannel(1);//微信
            order.setAddTime(new Date());

            //来源渠道
            if (ObjectUtil.isNotNull(channelType)){
                order.setChannel(channelType);
            }

            // 查询绑定的发型师
            Integer workerId = workerInviteService.getWorkerIdByUserId(userId);
            if(workerId!=null) {
                order.setInviterId(workerId);
                order.setWorkerId(workerId);
                Worker worker = workerService.getById(workerId);
                order.setWorkerName(worker.getName());
            }

            String body = memberDetail.getName();
            try {
                StoreDTO storeDTO = storeService.getBaseMapper().findByWorkerId(workerId);
                if (storeDTO != null) {
                    order.setStoreId(storeDTO.getId());
                    order.setStoreName(storeDTO.getTitle());
                    body = order.getStoreName().concat("-").concat(body);
                }
            } catch (Exception e) {
                e.printStackTrace();
                LogUtil.writeErrorLog(e.getMessage());
            }

            order.setIsUserMember(IBaseEnum.valueOf(OrderIsUserMemberEnum.class, isUserMember));
            orderService.setCustomerType(order);
            order.insert();

            WxPayBase wxPayBean = WxPayBeanFactory.getInstance(userService.getUserInviterStore(userId), 1);

            // 微信支付请求参数
            Map<String, Object> params = UnifiedOrderModel
                    .builder()
                    .appid(wxPayBean.getAppId())
                    .mch_id(wxPayBean.getMchId())
                    .nonce_str(RandomUtil.randomString(32))
                    .body(body)
                    .out_trade_no(orderNo)
                    .total_fee(String.valueOf(goodsTotal))
                    .spbill_create_ip(StringUtil.getIpAddr(request))
                    .notify_url(wxPayBean.getDomain().concat("/client/notify/member-detail"))
                    .trade_type(wxPayBean.getTradeType())
                    .openid(userService.getOpenIdById(userId))
                    .build()
                    .createSign(wxPayBean.getKey(), SignType.MD5);
            LogUtil.writePayLog("购买会员权益请求参数:{params:{}}", params);
            // 返回 xml 格式表单
            String xmlResult = WxPayApi.pushOrder(params);
            UnifiedOrderModelResponse response = WxPayKit.xmlToBean(xmlResult, UnifiedOrderModelResponse.class);
            LogUtil.writePayLog("购买会员权益请求返回参数={}", response.toString());
            // 判断请求结果
            WxPayKit.codeIsOk(response);

            // 微信支付返回的预支付id
            String prepayId = response.getPrepay_id();
            // 二次签名，构建公众号唤起支付的参数,这里的签名方式要与上面统一下单请求签名方式保持一致
            Map<String, Object> packageParams = WxPayKit.miniAppPrepayIdCreateSign(
                    wxPayBean.getAppId(),
                    prepayId,
                    wxPayBean.getKey(),
                    SignType.MD5);
            // 将二次签名构建的数据返回给前端并唤起小程序支付
            packageParams.put("orderId", order.getId());
            return Result.ok(packageParams);
        }catch (Exception e){
            System.out.println(e.getMessage());
            return null;
        }
    }


    /**
     * 检查是否符合购买权益的条件
     *
     * @param onlyBuy  是否能单独购买 false不能单独购买(只有会员能购买) true能单独购买(所有人都能买)
     * @param isMember 是否是会员
     * @return 可以购买返回 true
     */
    private Boolean checkPayMemberDetail(Boolean onlyBuy, Boolean isMember) {
        // 是否全部人都能买
        if (onlyBuy) {
            return true;
        }
        if (isMember) {
            return true;
        }
        return false;
    }


    @Description("定时清除取消支付所占用的券")
    @Scheduled(cron = "0 0 0 * * *")
    public void cancelScheduled() {
        QueryWrapper<CouponCode> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("status", 0)
                .isNotNull("user_id");
        List<CouponCode> couponCodeList = new CouponCode().selectList(queryWrapper);
        couponCodeList.forEach(couponCode -> {
            couponCode.setUserId(null);
            couponCode.updateById();
        });
    }

    @ApiOperation("是否符合购买条件")
    @GetMapping("/buy-condition")
    public Boolean condition(Integer userId) {
        return true;
    }


}